// Package dao
// Created by GoLand
// @User: lenora
// @Date: 2024/1/17
// @Time: 13:27

package article

import (
	"context"
	"errors"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
	"time"
)

func NewGormArticleDao(db *gorm.DB) ArticleDao {
	return &GormArticleDao{db: db}
}

type GormArticleDao struct {
	db *gorm.DB
}

func (dao *GormArticleDao) ListByOffset(ctx context.Context, start time.Time, offset, size int) ([]Articles, error) {
	list := make([]Articles, 0, size)
	err := dao.db.WithContext(ctx).
		Where("utime < ? and status = 2", start.UnixMilli()).
		Offset(offset).
		Limit(size).
		Find(&list).
		Error
	return list, err
}

func (dao *GormArticleDao) GetById(ctx context.Context, id uint64) (Articles, error) {
	var art Articles
	err := dao.db.WithContext(ctx).Where("id = ?", id).First(&art).Error
	return art, err
}

func (dao *GormArticleDao) GetByAuthor(ctx context.Context, uid uint64, offset, limit int) ([]Articles, error) {
	var list []Articles
	err := dao.db.WithContext(ctx).
		Where("creator = ?", uid).
		Offset(offset).
		Limit(limit).
		Order("utime desc").
		Find(&list).
		Error
	return list, err
}

func (dao *GormArticleDao) SyncStatus(ctx context.Context, uid, id uint64, status uint8) error {
	return dao.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		res := tx.Model(&Articles{}).
			Where("id = ? and creator = ?", id, uid).
			Updates(map[string]any{
				"utime":  time.Now().UnixMilli(),
				"status": status,
			})
		if res.Error != nil {
			return res.Error
		}
		if res.RowsAffected == 0 {
			return errors.New("撤回失败")
		}

		return tx.Model(&PublishArticle{}).
			Where("id = ? ", id).
			Updates(map[string]any{
				"utime":  time.Now().UnixMilli(),
				"status": status,
			}).Error
	})
}

func (dao *GormArticleDao) Sync(ctx context.Context, art Articles) (uint64, error) {
	//开启事务
	tx := dao.db.WithContext(ctx).Begin()
	if tx.Error != nil {
		return 0, tx.Error
	}

	var (
		id  = art.Id
		err error
	)

	self := NewGormArticleDao(tx)
	if art.Id == 0 {
		id, err = self.Insert(ctx, art)
	} else {
		err = self.UpdateById(ctx, art)
	}

	if err != nil {
		return 0, err
	}
	art.Id = id
	now := time.Now().UnixMilli()
	pubArt := PublishArticle(art)
	pubArt.Utime = now
	pubArt.Ctime = now
	// INSERT `pubArt` ON DUPLICATE KEY SET `DoUpdates`
	err = tx.Clauses(clause.OnConflict{
		Columns: []clause.Column{{
			Name: "id",
		}}, //columns对mysql不生效,但可以兼容其他的orm
		DoUpdates: clause.Assignments(map[string]interface{}{
			"title":   pubArt.Title,
			"content": pubArt.Content,
			"utime":   pubArt.Utime,
			"status":  pubArt.Status,
		}),
	}).Create(&pubArt).Error
	if err != nil {
		return 0, err
	}
	tx.Commit()
	return id, nil
}

// SyncV1 Sync的闭包写法
func (dao *GormArticleDao) SyncV1(ctx context.Context, art Articles) (uint64, error) {
	var id = art.Id
	err := dao.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		var err error

		self := NewGormArticleDao(tx)
		if art.Id == 0 {
			id, err = self.Insert(ctx, art)
		} else {
			err = self.UpdateById(ctx, art)
		}

		if err != nil {
			return err
		}
		art.Id = id
		now := time.Now().UnixMilli()
		pubArt := PublishArticle(art)
		pubArt.Utime = now
		pubArt.Ctime = now
		// INSERT `pubArt` ON DUPLICATE KEY SET `DoUpdates`
		err = tx.Clauses(clause.OnConflict{
			Columns: []clause.Column{{
				Name: "id",
			}}, //columns对mysql不生效,但可以兼容其他的orm
			DoUpdates: clause.Assignments(map[string]interface{}{
				"title":   pubArt.Title,
				"content": pubArt.Content,
				"utime":   pubArt.Utime,
			}),
		}).Create(&pubArt).Error
		return err
	})
	if err != nil {
		return 0, err
	}
	return id, nil
}

func (dao *GormArticleDao) UpdateById(ctx context.Context, a Articles) error {
	a.Utime = time.Now().UnixMilli()
	res := dao.db.WithContext(ctx).
		Where("id = ? and creator = ?", a.Id, a.Creator).
		Updates(&a)
	if res.Error != nil {
		return res.Error
	}
	if res.RowsAffected == 0 {
		return errors.New("更新失败")
	}
	return nil
}

func (dao *GormArticleDao) Insert(ctx context.Context, a Articles) (uint64, error) {
	a.Ctime = time.Now().UnixMilli()
	err := dao.db.WithContext(ctx).Create(&a).Error
	return a.Id, err
}
